/**
 * 
 */
package totalFileSize;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * @author mfan
 *
 */
public class TotalFileSizeConcurrent {

	/**
	 * @author mfan
	 *
	 */
	private class SubdirectoriesAndSize {
		
		private final long size;
		private final List<File> subdirectories;
		
		public SubdirectoriesAndSize(final long size, final List<File> subdirectories) {
			this.size = size;
			this.subdirectories = Collections.unmodifiableList(subdirectories);
		}
		
		public long getSize() {
			return this.size;
		}
		
		public List<File> getSubdirectories() {
			return this.subdirectories;
		}
	}
	
	/**
	 * Get total size of all files in "path", not including files in 
	 * its sub-directories, and all sub-directories in "path"
	 * 
	 * @param path
	 * @return
	 */
	private SubdirectoriesAndSize getSubsizeAndSubdirs(final File path) {
		
		long subTotalSize = 0;
		final List<File> subdirectories = new ArrayList<>();
		
		if (path.isDirectory()) {
			final File[] files = path.listFiles();
			if (files != null) {
				for (File aFile : files) {
					if (aFile.isDirectory()) {
						subdirectories.add(aFile);
					} else {
						subTotalSize += aFile.length();
					}
				}
			}
		}
		return new SubdirectoriesAndSize(subTotalSize, subdirectories);
	}
	
	/**
	 * @param path
	 * @return
	 * @throws ExecutionException 
	 * @throws InterruptedException 
	 * @throws TimeoutException 
	 */
	public long getTotalFileSizeInDir(final File path) throws InterruptedException, ExecutionException, TimeoutException {
		
		if (path.isFile()) {
			return path.length();
		}
		
		final List<File> directories = new ArrayList<>();
		directories.add(path);
		
		final ExecutorService threadPool = Executors.newFixedThreadPool(40);
		
		try {
			long totalSize = 0;
			
			while (!directories.isEmpty()) {
				final List<Future<SubdirectoriesAndSize>> partialFileSizes = new ArrayList<>();
				for (final File subDir : directories) {
					partialFileSizes.add(threadPool.submit(new Callable<SubdirectoriesAndSize>() {

						@Override
						public SubdirectoriesAndSize call() throws Exception {
							// TODO Auto-generated method stub
							return getSubsizeAndSubdirs(subDir);
						}
						
					}));					
				}
				
				directories.clear();
				
				for (Future<SubdirectoriesAndSize> aPartialSize : partialFileSizes) {
					final SubdirectoriesAndSize aSubSize = aPartialSize.get(100, TimeUnit.SECONDS);
					totalSize += aSubSize.getSize();
					directories.addAll(aSubSize.getSubdirectories());
				}
					
			}
			
			return totalSize;
			
		} finally {
			threadPool.shutdown();
		}
	}
	
	public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
		
		final String targetPath = "/usr";
		System.out.println("Start find total size of path: " + targetPath);
		final long start = System.nanoTime();
		final long totalSize = (new TotalFileSizeConcurrent()).getTotalFileSizeInDir(new File(targetPath));
		final long end = System.nanoTime();
		System.out.printf("Total file size of path: %s is: %d.%n", targetPath, totalSize);
		System.out.println("Time used is: " + (end - start)/1.0e9);
	}
}
